#if UNITY_EDITOR
using UnityEditor;
using UnityEngine.AI;
using System.Reflection;
#endif
using UnityEngine;
using Devil;

namespace GameToolkit
{

    public interface IVolumetric
    {
        Bounds localBounds { get; }
        Matrix4x4 localToWorldMatrix { get; }
        Matrix4x4 worldToLocalMatrix { get; }
    }


    [System.AttributeUsage(System.AttributeTargets.Class, Inherited = true)]
    public class UnifyScaleAttribute : System.Attribute
    {
    }

    [DisallowMultipleComponent]
    public class VolumetricBounds : MonoBehaviour, IVolumetric, IValidator
#if UNITY_EDITOR && !SIMULATION
        , ISerializationCallbackReceiver
#endif
    {
#if UNITY_EDITOR
        public static Color HashColor(System.Type type, bool transparency)
        {
            var f = 1f / 255f;
            var code = (uint)type.FullName.GetHashCode() ^ 0x9af01e02u;
            Color color;
            color.r = ((code & 0xff000000u) >> 24) * f;
            color.g = ((code & 0xff0000u) >> 16) * f;
            color.b = ((code & 0xff00u) >> 8) * f;
            color.a = transparency ? ((code & 0xffu) * f) : 1f;
            return color;
        }

        protected VolumetricBounds()
        {
            defaultColor = HashColor(GetType(), false);
        }

        readonly Color defaultColor;
#endif

        public static Bounds CalcuateBounds(Transform space, Transform local, Vector3 localCenter, Vector3 localSize)
        {
            if (local == null)
                return new Bounds(localCenter, localSize);
            else if (space == null)
                return CalcuateBounds(local.localToWorldMatrix, localCenter, localSize);
            else if (space == local)
                return new Bounds(localCenter, localSize);
            else
                return CalcuateBounds(space.worldToLocalMatrix * local.localToWorldMatrix, localCenter, localSize);
        }

        public static Bounds CalcuateBounds(Transform space, Transform local, Bounds bounds)
        {
            if (local == null)
                return bounds;
            else if (space == null)
                return CalcuateBounds(local.localToWorldMatrix, bounds);
            else if (space == local)
                return bounds;
            else
                return CalcuateBounds(space.worldToLocalMatrix * local.localToWorldMatrix, bounds);
        }

        public static Bounds CalcuateBounds(Matrix4x4 matrix, Vector3 localCenter, Vector3 localSize)
        {
            Bounds bd;
            MathExt.ToLocalBounds(matrix, new Bounds(localCenter, localSize), out bd);
            return bd;
        }

        public static Bounds CalcuateBounds(Matrix4x4 matrix, Bounds localBounds)
        {
            return CalcuateBounds(matrix, localBounds.center, localBounds.size);
        }

        public static void Snape(VolumetricBounds bounds, Vector3 tileSize)
        {
            var size = bounds.m_BoundsSize;
            if (tileSize.x > 0f)
                size.x -= Mathf.Repeat(size.x, tileSize.x);
            if (tileSize.y > 0f)
                size.y -= Mathf.Repeat(size.y, tileSize.y);
            if (tileSize.z > 0f)
                size.z -= Mathf.Repeat(size.z, tileSize.z);
            var min = bounds.m_BoundsCenter - bounds.m_BoundsSize * 0.5f;
            var center = min + size * 0.5f;
            if (MathExt.IsDifferent(center, bounds.m_BoundsCenter, 0.01f) || MathExt.IsDifferent(size, bounds.m_BoundsSize, 0.01f))
            {
                bounds.m_BoundsCenter = center;
                bounds.m_BoundsSize = size;
#if UNITY_EDITOR
                EditorUtility.SetDirty(bounds);
#endif
            }
        }

#pragma warning disable CS0649
        public Vector3 m_BoundsCenter;
        public Vector3 m_BoundsSize = Vector3.one;
        //保持缩放比例永远为1
        [SerializeField, HideInInspector]
        private bool m_UnifyScale;
        public bool IsUnityScale { get { return m_UnifyScale; } }
#pragma warning restore
        public virtual Color color
        {
            get
            {
#if UNITY_EDITOR
                return defaultColor;
#else
                return Color.gray;
#endif
            }
        }
        public virtual bool IsValid { get { return this != null; } }

        public virtual Bounds bounds
        {
            get
            {
                var matrix = m_UnifyScale ? Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one) : localToWorldMatrix;
                return CalcuateBounds(matrix, m_BoundsCenter, m_BoundsSize);
            }
        }


        protected BoxCollider mBox;
        public Bounds localBounds { get { return new Bounds(m_BoundsCenter, m_BoundsSize); } }
        protected Matrix4x4 mToLocal;
        protected Matrix4x4 mToWorld;
        public Matrix4x4 localToWorldMatrix
        {
            get
            {
                if (ParallelUtils.IsMainThread && this != null)
                    mToWorld = transform.localToWorldMatrix;
                return mToWorld;
            }
        }
        public Matrix4x4 worldToLocalMatrix
        {
            get
            {
                if (ParallelUtils.IsMainThread && this != null)
                    mToLocal = transform.worldToLocalMatrix;
                return mToLocal;
            }
        }
        public bool IsInBounds(Vector3 worldPosition)
        {
            return localBounds.Contains(transform.worldToLocalMatrix.MultiplyPoint(worldPosition));
        }

        public Bounds CalculateLocalBounds(Transform space)
        {
            if (space == transform)
                return localBounds;
            else if (space == null)
                return bounds;
            else
                return CalcuateBounds(space.worldToLocalMatrix * transform.localToWorldMatrix, m_BoundsCenter, m_BoundsSize);
        }

        public void GetWorldSpaceCube(out Vector3 pos, out Vector3 size, out Quaternion rot)
        {
            if (m_UnifyScale)
            {
                pos = transform.localToWorldMatrix.MultiplyPoint(m_BoundsCenter);
                size = m_BoundsSize;
                rot = transform.rotation;
            }
            else
            {
                pos = transform.localToWorldMatrix.MultiplyPoint(m_BoundsSize);
                size = MathExt.Mul(m_BoundsSize, transform.lossyScale);
                rot = transform.rotation;
            }
        }

        protected virtual void Awake()
        {
            mToLocal = transform.worldToLocalMatrix;
            mToWorld = transform.localToWorldMatrix;
        }

#if !SIMULATION && UNITY_EDITOR
        public virtual void OnBeforeSerialize()
        {
            var tp = GetType();
            m_UnifyScale = tp.GetCustomAttribute<UnifyScaleAttribute>() != null;
        }

        public virtual void OnAfterDeserialize()
        {

        }
#endif

#if UNITY_EDITOR

        protected virtual bool DrawGizmos { get { return Selection.activeGameObject == gameObject; } }
        public bool IsEditMode { get; set; }

        [MenuItem("CONTEXT/BoxCollider/Get Geometry Bounds")]
        static void SetBoxSize(MenuCommand command)
        {
            var box = command.context as BoxCollider;
            if (box != null)
            {
                Undo.RecordObject(box, "Get Bounds");
                var bd = MathExt.CalculateLocalBounds(box.gameObject, true);
                box.center = bd.center;
                box.size = bd.size;
                EditorUtility.SetDirty(box);
            }
        }

        [MenuItem("CONTEXT/BoxCollider/Get Volume Bounds")]
        static void SetBoxVolume(MenuCommand command)
        {
            var box = command.context as BoxCollider;
            if (box != null)
            {
                var volume = box.GetComponent<VolumetricBounds>();
                if (volume == null)
                    return;
                Undo.RecordObject(box, "Get Bounds");
                box.center = volume.m_BoundsCenter;
                box.size = volume.m_BoundsSize;
                EditorUtility.SetDirty(box);
            }
        }

        [MenuItem("CONTEXT/NavMeshObstacle/Get Geometry Bounds")]
        static void SetNavObstacleSize(MenuCommand command)
        {
            var box = command.context as NavMeshObstacle;
            if (box != null)
            {
                Undo.RecordObject(box, "Get Bounds");
                var bd = MathExt.CalculateLocalBounds(box.gameObject, true);
                box.center = bd.center;
                box.size = bd.size;
                EditorUtility.SetDirty(box);
            }
        }

        [MenuItem("CONTEXT/NavMeshObstacle/Get Volume Bounds")]
        static void SetNavObstacleVolume(MenuCommand command)
        {
            var box = command.context as NavMeshObstacle;
            if (box != null)
            {
                var volume = box.GetComponent<VolumetricBounds>();
                if (volume == null)
                    return;
                Undo.RecordObject(box, "Get Bounds");
                box.center = volume.m_BoundsCenter;
                box.size = volume.m_BoundsSize;
                EditorUtility.SetDirty(box);
            }
        }

        [ContextMenu("Get Geometry Bounds")]
        void GetGeometryBounds()
        {
            Undo.RecordObject(this, "Get Bounds");
            var bd = MathExt.CalculateLocalBounds(gameObject, true);
            m_BoundsCenter = bd.center;
            m_BoundsSize = bd.size;
            UnityEditor.EditorUtility.SetDirty(this);
            //if (gameObject.scene.IsValid())
            //    UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(gameObject.scene);
        }

        [ContextMenu("Get BoxCollider Bounds")]
        void GetBoxSize()
        {
            var box = GetComponent<BoxCollider>();
            if (box != null)
            {
                Undo.RecordObject(this, "Get Bounds");
                m_BoundsCenter = box.center;
                m_BoundsSize = box.size;
                UnityEditor.EditorUtility.SetDirty(this);
            }
        }

        public void OnDrawGizmosSelected()
        {
            if (DrawGizmos)
            {
                DoDrawGizmos();
            }
        }

        protected virtual void DoDrawGizmos()
        {
            if (IsEditMode)
            {
                Gizmos.color = Color.gray * 0.5f;
                Gizmos.matrix = Matrix4x4.identity;
                var bd = bounds;
                Gizmos.DrawWireCube(bd.center, bd.size);
            }

            Gizmos.matrix = m_UnifyScale ? Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one) : transform.localToWorldMatrix;
            Gizmos.color = color;
            Gizmos.DrawWireCube(m_BoundsCenter, m_BoundsSize);
        }

#endif
    }
}